/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.acegisecurity.intercept.web;

import org.acegisecurity.intercept.AbstractSecurityInterceptor;
import org.acegisecurity.intercept.InterceptorStatusToken;
import org.acegisecurity.intercept.ObjectDefinitionSource;
import org.acegisecurity.util.UrlUtils;
import org.acegisecurity.concurrent.SessionRegistry ;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Performs security handling of HTTP resources via a filter implementation.
 * <p>
 * The <code>ObjectDefinitionSource</code> required by this security interceptor
 * is of type {@link FilterInvocationDefinitionSource}.
 * </p>
 * <P>
 * Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
 * </p>
 * 
 * @author Ben Alex
 * @version $Id: FilterSecurityInterceptor.java,v 1.3 2010/02/26 08:06:26 cvs
 *          Exp $
 */
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor
		implements Filter {
	// ~ Static fields/initializers
	// =====================================================================================
	
	private static final String					FILTER_APPLIED			= "__acegi_filterSecurityInterceptor_filterApplied";
	
	// ~ Instance fields
	// ================================================================================================
	
	private FilterInvocationDefinitionSource	objectDefinitionSource;
//	private PortResolver				portResolver				= new PortResolverImpl ( );
	private boolean								observeOncePerRequest	= true;
	
	private final PathMatcher					pathMatcher				= new AntPathMatcher ( );
	private boolean								useAntPath				= false;
	private String                              DEFAULTVALID_VALUE_Y    = "yes" ; 
	private String                              DEFAULTVALID_VALUE_N    = "no" ; 	
	private String								defaultValid			= DEFAULTVALID_VALUE_N;
	private List								excludesURL				= new ArrayList ( 0 );
	private List								excludesURI				= new ArrayList ( 0 );
	private String								expUrl					= null;
	private SessionRegistry                     sessionRegistry         = null ;
	private boolean                             mcase                   = true ; 
	
	// ~ Methods
	// ========================================================================================================
	
	/**
	 * Not used (we rely on IoC container lifecycle services instead)
	 */
	public void destroy ( ) {
	}
	
	/**
	 * Method that is actually called by the filter chain. Simply delegates to
	 * the {@link #invoke(FilterInvocation)} method.
	 * 
	 * @param request
	 *            the servlet request
	 * @param response
	 *            the servlet response
	 * @param chain
	 *            the filter chain
	 * 
	 * @throws IOException
	 *             if the filter chain fails
	 * @throws ServletException
	 *             if the filter chain fails
	 */
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    	javax.servlet.http.HttpServletRequest  hrequest = ( javax.servlet.http.HttpServletRequest ) request ; 
    	javax.servlet.http.HttpServletResponse hresponse = ( javax.servlet.http.HttpServletResponse ) response ;
		String url = UrlUtils.buildRequestUrl ( hrequest.getServletPath ( ) ,
				hrequest.getRequestURI ( ) , hrequest.getContextPath ( ) ,
				hrequest.getPathInfo ( ) , hrequest.getQueryString ( ) );
		String uri = hrequest.getRequestURI ( ) ;
	    if ( mcase ) {
	    	url = url.toLowerCase ( ) ;
	    } 
	    
    	boolean mark = false ; 
    	Object sessionInfo = null ;
    	if (  sessionRegistry.getSessionInformation ( hrequest.getSession ( ).getId ( )  ) != null )
    		sessionInfo = sessionRegistry.getSessionInformation ( hrequest.getSession ( ).getId ( )  ) ;
    	
		if ( DEFAULTVALID_VALUE_Y.equals (  getDefaultValid ( ) ) ) {
			for ( java.util.Iterator iter = excludesURI.iterator ( ) ; iter.hasNext ( ) ;  ) {
				String exs = iter.next ( ).toString ( ) ; 
				if ( mcase  ) exs = exs.toLowerCase ( ) ;
				if ( pathMatcher.match ( exs , uri ) ) {
					mark = true ;
					break ; 
				}
			}    	
			
			if ( !mark )
				for ( java.util.Iterator iter = excludesURI.iterator ( ) ; iter.hasNext ( ) ;  ) {
					String exs = iter.next ( ).toString ( ) ; 
					if ( mcase  ) exs = exs.toLowerCase ( ) ;
					if ( pathMatcher.match ( exs , url ) ) {
						mark = true ;
						break ; 
					}
				}    					
			
			if ( mark == true  ) chain.doFilter (  request , response  ) ;
			else {
				if ( sessionInfo == null && ( expUrl != null && !"".equals ( expUrl.trim ( ) ) ) ) 	{
					sendRedirect( hrequest , hresponse , expUrl  ) ;
					return  ; 
				}			
		        FilterInvocation fi = new FilterInvocation(request, response, chain);
		        invoke(fi);				
			}
		}
		else {
			/*
			if ( sessionInfo == null && ( expUrl != null && !"".equals ( expUrl.trim ( ) ) ) ) { 
				sendRedirect( hrequest , hresponse , expUrl  ) ;
				return  ; 
			} */
	        FilterInvocation fi = new FilterInvocation(request, response, chain);
	        invoke(fi);
		}
    }
	
	public FilterInvocationDefinitionSource getObjectDefinitionSource ( ) {
		return this.objectDefinitionSource;
	}
	
	public Class getSecureObjectClass ( ) {
		return FilterInvocation.class;
	}
	
	/**
	 * Not used (we rely on IoC container lifecycle services instead)
	 * 
	 * @param arg0
	 *            ignored
	 * 
	 * @throws ServletException
	 *             never thrown
	 */
	public void init ( FilterConfig arg0 ) throws ServletException {
	}
	
	public void invoke ( FilterInvocation fi ) throws IOException ,
			ServletException {
		if ( ( fi.getRequest ( ) != null )
				&& ( fi.getRequest ( ).getAttribute ( FILTER_APPLIED ) != null )
				&& observeOncePerRequest ) {
			// filter already applied to this request and user wants us to
			// observce
			// once-per-request handling, so don't re-do security checking
			fi.getChain ( ).doFilter ( fi.getRequest ( ) , fi.getResponse ( ) );
		} else {
			// first time this request being called, so perform security
			// checking
			if ( fi.getRequest ( ) != null ) {
				fi.getRequest ( ).setAttribute ( FILTER_APPLIED , Boolean.TRUE );
			}
			
			InterceptorStatusToken token = super.beforeInvocation ( fi );
			
			try {
				fi.getChain ( ).doFilter ( fi.getRequest ( ) ,
						fi.getResponse ( ) );
			} finally {
				super.afterInvocation ( token , null );
			}
		}
	}
	
	/**
	 * Indicates whether once-per-request handling will be observed. By default
	 * this is <code>true</code>, meaning the
	 * <code>FilterSecurityInterceptor</code> will only execute
	 * once-per-request. Sometimes users may wish it to execute more than once
	 * per request, such as when JSP forwards are being used and filter security
	 * is desired on each included fragment of the HTTP request.
	 * 
	 * @return <code>true</code> (the default) if once-per-request is honoured,
	 *         otherwise <code>false</code> if
	 *         <code>FilterSecurityInterceptor</code> will enforce
	 *         authorizations for each and every fragment of the HTTP request.
	 */
	public boolean isObserveOncePerRequest ( ) {
		return observeOncePerRequest;
	}
	
	public ObjectDefinitionSource obtainObjectDefinitionSource ( ) {
		return this.objectDefinitionSource;
	}
	
	public void setObjectDefinitionSource (
			FilterInvocationDefinitionSource newSource ) {
		this.objectDefinitionSource = newSource;
	}
	
	public void setObserveOncePerRequest ( boolean observeOncePerRequest ) {
		this.observeOncePerRequest = observeOncePerRequest;
	}
	
	public boolean isUseAntPath ( ) {
		return useAntPath;
	}
	
	public void setUseAntPath ( boolean useAntPath ) {
		this.useAntPath = useAntPath;
	}
	
	public String getDefaultValid ( ) {
		return defaultValid;
	}
	
	public void setDefaultValid ( String defaultValid ) {
		this.defaultValid = defaultValid;
	}
	
	public List getExcludesURL ( ) {
		return excludesURL;
	}

	public void setExcludesURL ( List excludesURL ) {
		this.excludesURL = excludesURL;
	}

	public List getExcludesURI ( ) {
		return excludesURI;
	}

	public void setExcludesURI ( List excludesURI ) {
		this.excludesURI = excludesURI;
	}

	public String getExpUrl ( ) {
		return expUrl;
	}

	public void setExpUrl ( String expUrl ) {
		this.expUrl = expUrl;
	}

	public SessionRegistry getSessionRegistry ( ) {
		return sessionRegistry;
	}

	public void setSessionRegistry ( SessionRegistry sessionRegistry ) {
		this.sessionRegistry = sessionRegistry;
	}
	
	
	public boolean getMcase ( ) {
		return mcase;
	}

	public void setMcase ( boolean mcase ) {
		this.mcase = mcase;
	}

	protected void sendRedirect ( HttpServletRequest request ,
			HttpServletResponse response , String url ) throws IOException {
		if ( ! url.startsWith ( "http://" ) && ! url.startsWith ( "https://" ) ) {
			url = request.getContextPath ( ) + url;
		}
		response.sendRedirect ( response.encodeRedirectURL ( url ) );
	}
}
